using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using D2DEngine.Scene;
using D2DEngine.Core;

using AIDev.Components;
using D2DEngine.Components.AI;
using System;
using D2DEngine.Physics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision;
using FarseerPhysics.Factories;
using FarseerPhysics;
using D2DEngine.Util;
using AIDev.Components.Projectiles;
using D2DEngine.ParticleEffect;
using Microsoft.Xna.Framework.Audio;
using D2DEngine.Tile;
using D2DEngine;

namespace AIDev
{
    /// <summary>
    /// Collision categories
    /// Cat1 = Walls, Borders
    /// Cat2 = Normal enemies
    /// Cat3 = Special enemies
    /// Cat4 = Reserved enemies category
    /// Cat5 = Player
    /// Cat6 = Player projectiles
    /// </summary>
    public class Game1 : D2DEngine.Core.D2DGame
    {
        DebugViewXNA debugView;

        /// <summary>
        /// Game constructor (leave empty)
        /// </summary>
        public Game1()
            : base()
        {
        }


        /// <summary>
        /// Initializes the game settings
        /// </summary>
        protected override void InitGameSettings()
        {
            D2DSettings.ScreenWidth = 1920;
            D2DSettings.ScreenHeight = 1080;
            D2DSettings.Debug = true;

            Components.Add(new FPSComponent(this, "diagnosticFont", true));
        }
        /// <summary>
        /// Loads the ressources 
        /// </summary>
        /// <param name="content"></param>
        protected override void LoadRessources()
        {

            // Set up the physics debugger
            this.debugView = new DebugViewXNA(new World(Vector2.Zero));
            this.debugView.LoadContent(GraphicsDevice, Content);

            this.debugView.AppendFlags(DebugViewFlags.Shape);
            this.debugView.AppendFlags(DebugViewFlags.Joint);
            this.debugView.AppendFlags(DebugViewFlags.DebugPanel);
            this.debugView.AppendFlags(DebugViewFlags.ContactPoints);
            this.debugView.AppendFlags(DebugViewFlags.AABB);
            this.debugView.AppendFlags(DebugViewFlags.PerformanceGraph);
            this.debugView.AppendFlags(DebugViewFlags.PolygonPoints);
            this.debugView.AppendFlags(DebugViewFlags.CenterOfMass);
            this.debugView.DefaultShapeColor = Color.LightGray;
            this.debugView.SleepingShapeColor = Color.Orange;
        }

        /// <summary>
        /// Loads the game assets and add them to the database
        /// </summary>
        /// <param name="content"></param>
        protected override void LoadAssets()
        {
            D2DAssetDatabase.Instance.AddTexture("arrow", Content.Load<Texture2D>("whiteArrow"));
            D2DAssetDatabase.Instance.AddTexture("ball", Content.Load<Texture2D>("redBall"));
            D2DAssetDatabase.Instance.AddTexture("spikedBall", Content.Load<Texture2D>("spikedBall"));
            D2DAssetDatabase.Instance.AddTexture("effect_explosionFire", Content.Load<Texture2D>("explosion_fire"));

            D2DAssetDatabase.Instance.AddFont("font_menu", Content.Load<SpriteFont>("menu"));
            
            // Sounds
            D2DAssetDatabase.Instance.AddSound("Explosion", Content.Load<SoundEffect>("explosion"));
        }

        /// <summary>
        /// Loads the game objects (actors, map, scene object)
        /// </summary>
        protected override void LoadGame()
        {
            LoadLevel1();
            LoadContinueScene();
            
           this.sceneManager.CurrentSceneName = "Level1";
        }

        private void LoadLevel1()
        {
            D2DScene scene = new D2DScene();

            // Map
            D2DTileMap map = TileMapProcessor.Load("Content/map.xml", Content);
            scene.BoundingBox = new Rectangle(0, 0, map.WidthInPixels, map.HeightInPixels);
            scene.Register(map);

            // Particle effect
            D2DParticleEffect effect = new D2DParticleEffect();
            effect.Name = "Effect_explosionFire";
            effect.Sprite = new D2DSpriteComponent(D2DAssetDatabase.Instance.GetTexture("effect_explosionFire"));
            effect.Settings.MaxVelocity = 200.0f;
            effect.Settings.MinVelocity = 50.0f;
            effect.Settings.MinLifetime = 0.4f;
            effect.Settings.MaxLifetime = 0.5f;
            effect.Settings.MinScale = 0.1f;
            effect.Settings.MaxScale = 0.3f;
            effect.Settings.MinParticles = 50;
            effect.Settings.MaxParticles = 50;
            scene.Register(effect);

            // Projectile
            D2DActor projectile = new D2DActor();
            projectile.Name = "Projectile_missile";
            projectile.AddComponent(new D2DSpriteComponent(D2DAssetDatabase.Instance.GetTexture("arrow")));
            projectile.AddComponent(new MissileComponent());
            projectile.Body = BodyFactory.CreateRectangle(scene.World, D2DMath.ToMeters(20), D2DMath.ToMeters(20), 1.0f);
            projectile.Body.BodyType = BodyType.Dynamic;
            projectile.Body.Enabled = false;
            projectile.Body.CollidesWith = Category.All;
            D2DObjectDatabase.Instance.Insert(projectile);

            // Player
            D2DActor ball = new D2DActor();
            ball.Name = "Ball";
            ball.AddComponent(new D2DSpriteComponent(D2DAssetDatabase.Instance.GetTexture("ball")));
            PlayerControlComponent playerControl = new PlayerControlComponent();
            playerControl.OnPlayerDeath += OnPlayerDeath;
            ball.AddComponent(playerControl);
            ball.Body = BodyFactory.CreateCircle(scene.World, D2DMath.ToMeters(10), 10f);

            ball.Position = D2DMath.ToMeters(new Vector2(400, 500));

            scene.Register(ball);

            // Load map 
            LoadMapObstacles(map, scene);

            // Load map monsters
            LoadMapMonsters(map, scene);

            this.sceneManager.Scenes.Add("Level1", scene);
        }

        private void LoadContinueScene()
        {
            D2DScene continueScene = new D2DScene();

            D2DSceneObject continueText = new D2DSceneObject();
            continueText.AddComponent(new D2DTextComponent("You're dead, press Enter to continue....", D2DAssetDatabase.Instance.GetFont("font_menu")));
            continueText.Position = D2DMath.ToMeters(new Vector2(300, 300));

            continueScene.Register(continueText);
            this.sceneManager.Scenes.Add("ContinueScene", continueScene);
        }

        private void LoadMapMonsters(D2DTileMap map, D2DScene scene)
        {
            float tileSize = map.TileSet.TileSize;
            int[,] layout = map.MonsterLayer.Layout;
            int height = layout.GetLength(1);
            int width = layout.GetLength(0);
            D2DActor enemy;

            for (int y = 0; y < layout.GetLength(1); y++)
            {
                for (int x = 0; x < layout.GetLength(0); x++)
                {
                    if (layout[y, x] != 0)
                    {
                        enemy = new D2DActor();
                        enemy.Name = "Enemy";
                        enemy.AddComponent(new D2DSpriteComponent(D2DAssetDatabase.Instance.GetTexture("spikedBall")));
                        enemy.AddComponent(new EnemyComponent(Direction.South, 10));
                        enemy.Body = BodyFactory.CreateCircle(scene.World, D2DMath.ToMeters(20), 1.0f);
                        enemy.Position = D2DMath.ToMeters(new Vector2(
                                tileSize * x + tileSize / 2,
                                tileSize * y + tileSize / 2
                            ));

                        switch (layout[y, x])
                        {
                            case 1:
                                enemy.AddComponent(new EnemyComponent(Direction.North, D2DMath.RandomInt(2, 5)));
                            break;

                            case 2:
                                enemy.AddComponent(new EnemyComponent(Direction.East, D2DMath.RandomInt(2, 5)));
                            break;
                        }

                        scene.Register(enemy);
                    }
                }
            }
        }

        private void LoadMapObstacles(D2DTileMap map, D2DScene scene)
        {
            float tileSize = map.TileSet.TileSize;

            // Top border
            Body body = BodyFactory.CreateRectangle(scene.World, D2DMath.ToMeters(map.WidthInPixels), D2DMath.ToMeters(15), 1.0f);
            body.BodyType = BodyType.Static;
            body.Position = D2DMath.ToMeters(new Vector2(map.WidthInPixels / 2, 0));
            body.CollisionCategories = Category.All;

            // Bottom border
            body = BodyFactory.CreateRectangle(scene.World, D2DMath.ToMeters(map.WidthInPixels), D2DMath.ToMeters(15), 1.0f);
            body.BodyType = BodyType.Static;
            body.Position = D2DMath.ToMeters(new Vector2(map.WidthInPixels / 2, map.HeightInPixels));
            body.CollisionCategories = Category.All;

            // Left border
            body = BodyFactory.CreateRectangle(scene.World, D2DMath.ToMeters(15), D2DMath.ToMeters(map.HeightInPixels), 1.0f);
            body.BodyType = BodyType.Static;
            body.Position = D2DMath.ToMeters(new Vector2(0, map.HeightInPixels / 2));
            body.CollisionCategories = Category.All;

            // Right border
            body = BodyFactory.CreateRectangle(scene.World, D2DMath.ToMeters(15), D2DMath.ToMeters(map.HeightInPixels), 1.0f);
            body.BodyType = BodyType.Static;
            body.Position = D2DMath.ToMeters(new Vector2(map.WidthInPixels, map.HeightInPixels / 2));
            body.CollisionCategories = Category.All;

            int[,] layout = map.CollisionLayer.Layout;
            int height = layout.GetLength(1);
            int width = layout.GetLength(0);

            for (int y = 0; y < layout.GetLength(1); y++)
            {
                for (int x = 0; x < layout.GetLength(0); x++)
                {
                    if (layout[y, x] == 0)
                    {
                        body = BodyFactory.CreateRectangle(scene.World, D2DMath.ToMeters(tileSize), D2DMath.ToMeters(tileSize), 1.0f);
                        body.BodyType = BodyType.Static;
                        body.Position = D2DMath.ToMeters(new Vector2(
                                tileSize * x + tileSize / 2,
                                tileSize * y + tileSize / 2
                            ));
                        body.CollisionCategories = Category.All;
                    }
                }
            }
        }

        /// <summary>
        /// Updates the whole game including all scenes.
        /// </summary>
        /// <param name="gameTime"></param>
        protected override void Update(GameTime gameTime)
        {
            // Fix the camera to the player
            //D2DSceneManager.Instance.CurrentScene.Camera.LockToTarget(D2DSceneManager.Instance.CurrentScene.FindObject<D2DActor>("Ball"), D2DMath.ToMeters(D2DSettings.ScreenWidth), D2DMath.ToMeters(D2DSettings.ScreenHeight));
            D2DSceneManager.Instance.CurrentScene.Camera.ClampToArea(this.sceneManager.CurrentScene.BoundingBox, D2DSettings.ScreenWidth, D2DSettings.ScreenHeight);

            HandleInput();

            base.Update(gameTime);
        }

        private void OnPlayerDeath()
        {
            this.sceneManager.CurrentSceneName = "ContinueScene";
        }


        /// <summary>
        /// Handles the generic inputs
        /// </summary>
        private void HandleInput()
        {
            if (D2DInputManager.Instance.IsKeyDown(Keys.Left))
                this.sceneManager.CurrentScene.Camera.Position += new Vector2(-1f, 0);

            if (D2DInputManager.Instance.IsKeyDown(Keys.Right))
                this.sceneManager.CurrentScene.Camera.Position += new Vector2(1f, 0.0f);

            if (D2DInputManager.Instance.IsKeyDown(Keys.Down))
                this.sceneManager.CurrentScene.Camera.Position += new Vector2(0.0f, 1f);

            if (D2DInputManager.Instance.IsKeyDown(Keys.Up))
                this.sceneManager.CurrentScene.Camera.Position += new Vector2(0.0f, -1f);

            if (D2DInputManager.Instance.IsKeyPressed(Keys.F5))
            {
                this.sceneManager.CurrentSceneName = "ContinueScene";
            }

            if (this.sceneManager.CurrentSceneName == "ContinueScene" && D2DInputManager.Instance.IsKeyPressed(Keys.Enter))
            {
                this.sceneManager.Scenes.Remove("Level1");
                LoadLevel1();
                this.sceneManager.CurrentSceneName = "Level1";
            }

            if (D2DInputManager.Instance.IsKeyPressed(Keys.F1))
            {
                D2DSettings.Debug = !D2DSettings.Debug;
            }

            
        }

        /// <summary>
        /// Draws the game
        /// </summary>
        /// <param name="gameTime"></param>
        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            // Draw the physics debugger
            if (D2DSettings.Debug)
            {
                this.debugView.World = this.sceneManager.CurrentScene.World;

                D2DCamera camera = this.sceneManager.CurrentScene.Camera;

                // Screen projection
                Matrix projection = Matrix.CreateOrthographicOffCenter(0, D2DMath.ToMeters(GraphicsDevice.Viewport.Width), D2DMath.ToMeters(GraphicsDevice.Viewport.Height), 0, 0, 1f);
                
                // The camera view (Displaying where stuff is in the world)
                Matrix view = Matrix.CreateTranslation(new Vector3(-D2DMath.ToMeters(camera.Position.X), -D2DMath.ToMeters(camera.Position.Y), 0f));

                this.debugView.RenderDebugData(ref projection, ref view);
            }
        }

    }
}
